import { ErrorType } from "/@/utils/constants";
import { message } from "ant-design-vue";

/**
 * 监听全局异常
 *  完善算法，需要判断是否是同一条错误，避免重复上报
 *
 * @see https://juejin.cn/post/6987681953424080926
 * @param {Object} app
 */
export function setupListenerError (app) {
  app.config.errorHandler = vueErrorHandler
  window.onerror = scriptErrorHandler
  // 这种监听方式，可以监听到 资源（如图片或脚本）加载失败 的异常
  window.removeEventListener('error', eventListerError)
  window.addEventListener('error', eventListerError, true)
  window.removeEventListener('unhandledrejection', eventListerError)
  window.addEventListener('unhandledrejection', eventListerError, true)
  // console.error = function () {
  //   console.log('console.error: ', ...arguments)
  // }
}

/**
 * 打印错误
 * @param {ErrorEvent | PromiseRejectionEvent} error
 */
function eventListerError (error) {
  let logData = null
  let logTitle = ''
  if (error instanceof ErrorEvent) {
    const target = error.target || error.srcElement || {}
    logTitle = '捕获 window.addEventListener error'
    logData = {
      type: ErrorType.RESOURCE,
      name: 'Resource Error!',
      file: target.currentSrc,
      detail: JSON.stringify({
        tagName: target.localName,
        html: target.outerHTML,
        type: error.type,
      }),
      url: window.location.href,
      stack: 'resource is not found',
      message: error.target.localName + ' is load error',
    }
  } else if (error instanceof PromiseRejectionEvent) {
    logTitle = '捕获 window.addEventListener unhandledrejection'
    const reason = error.reason
    let message = reason.message
    if (reason.isAxiosError) {
      message = `[${reason.config.url}] ${message}`
    }
    logData = {
      type: ErrorType.PROMISE,
      name: 'Promise Error!',
      file: 'none',
      detail: 'promise error!',
      url: window.location.href,
      stack: reason.stack || 'promise error!',
      message,
    }
  }
  consoleLog(logTitle, logData, error)
}

/**
 * vue 错误
 * @param {*} err
 * @param {Object} instance
 * @param {string} info
 */
function vueErrorHandler (err, instance, info) {
  const { name, path } = formatComponentName(instance);
  consoleLog('捕获 vue 错误', {
    type: ErrorType.VUE,
    name,
    file: path,
    message: err.message,
    stack: processStackMsg(err),
    detail: info,
    url: window.location.href,
  }, err)
}

/**
 * js 错误，如 使用未声明的变量
 * @param {Event | String} event 错误信息
 * @param {String} source 出错文件
 * @param {Number} lineno 行号
 * @param {Number} colno 列号
 * @param {Error} error Error对象
 */
function scriptErrorHandler (event, source, lineno, colno, error) {
  if (event === 'Script error.' && !source) {
    return false;
  }
  consoleLog('js 错误', {
    type: ErrorType.SCRIPT,
    name: source ? source.substr(source.lastIndexOf('/') + 1) : 'script',
    file: source,
    detail: `lineno: ${lineno || 0}, colno: ${colno || 0}`,
    url: window.location.href,
    message: event,
    stack: error?.stack ?? ''
  }, error)
}

/**
 * 获取 vue 错误的组件名和路径
 * @param {Object} vm
 */
function formatComponentName (vm) {
  if (vm.$root === vm) {
    return {
      name: 'root',
      path: 'root',
    };
  }

  const options = vm.$options;
  if (!options) {
    return {
      name: 'anonymous',
      path: 'anonymous',
    };
  }
  const name = options.name || options._componentTag;
  return {
    name: name,
    path: options.__file,
  };
}

/**
 * 处理错误堆栈信息
 * @param {Error} error
 */
function processStackMsg (error) {
  if (!error.stack) {
    return '';
  }
  // 错误行数定位这里提示的信息与浏览器真实打印的信息不太符合（不是这段代码的问题）
  let stack = error.stack.replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content
                    .replace(/\bat\b/gi, '@') // At in chrome, @ in ff
                    .split('@') // Split information with @
                    .slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10
                    .map((v) => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces
                    .join('~') // Manually add separators for later display
                    .replace(/\?[^:]+/gi, ''); // Remove redundant parameters of js file links (?x=1 and the like)
  const msg = error.toString();
  if (stack.indexOf(msg) < 0) {
    stack = msg + '@' + stack;
  }
  return stack;
}

/**
 * 打印控制台信息，后续需要记录在 vuex 里
 * @param {string} title 日志标题
 * @param {Object} data 数据
 * @param {Error | ErrorEvent | PromiseRejectionEvent} error 错误对象
 */
function consoleLog (title, data, error) {
  message.error(`${title}，请打开控制台检查`)
  console.group(title)
  console.table(data)
  console.log('原错误：', error)
  console.groupEnd()
}
